﻿using Consul;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Hosting.Server.Features;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.NetworkInformation;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Helpers
{
    public static class ConsulExtensions
    {
        /// <summary>
        /// 添加consul,注册IConsulClient,并注册自身服务内网ip和端口,docker部署需要添加参数--net host,iis部署需要配置端口号
        /// </summary>
        public static IServiceCollection AddConsulz(this IServiceCollection services, IConfiguration configuration, Action<ConsulOptions> configure = null)
        {
            services.AddOptions<ConsulOptions>()
                .Bind(configuration.GetSection(ConsulOptions.SectionName))
                .ValidateDataAnnotations();
            services.PostConfigure<ConsulOptions>(x =>
            {
                configure?.Invoke(x);
                x.serviceName ??= AppDomain.CurrentDomain.FriendlyName;
            });
            services.AddSingleton<ConsulOptions>(x => x.GetRequiredService<IOptions<ConsulOptions>>().Value);

            services.AddSingleton<IStartupFilter, ConsulStartupFilter>();
            services.AddSingleton<IConsulClient>(p => new ConsulClient(x => x.Address = new Uri(p.GetRequiredService<ConsulOptions>().url)));
            return services;
        }

        /// <summary>
        /// 向consul注册服务
        /// </summary>
        internal static void UseConsulz(this IApplicationBuilder app)
        {
            try
            {
                ConsulHelper.ConsulClient = app.ApplicationServices.GetRequiredService<IConsulClient>();

                var consulOptions = app.ApplicationServices.GetRequiredService<ConsulOptions>();

                //var features = app.Properties["server.Features"] as FeatureCollection;
                var features = app.ServerFeatures;
                var consulClient = app.ApplicationServices.GetRequiredService<IConsulClient>();
                var lifetime = app.ApplicationServices.GetRequiredService<IHostApplicationLifetime>();
                var hostEvn = app.ApplicationServices.GetService<IHostEnvironment>();

                //获取端口号
                consulOptions.servicePort ??= new Uri(features.Get<IServerAddressesFeature>()
                    .Addresses
                    .FirstOrDefault().Replace("*", "0.0.0.0")).Port;

                //获取ip
                using var client = new WebClientx();
                var firstIpV4Address = client.DownloadString(consulOptions.IpParserUrl);

                ThreadPool.GetMinThreads(out var minWorkerThreads, out var minCompletionPortThreads);
                ThreadPool.GetMaxThreads(out var maxWorkerThreads, out var maxCompletionPortThreads);

                //NetworkInterface[] adapters = NetworkInterface.GetAllNetworkInterfaces(); //适配器
                //var firstIpV4Address = consulOptions.serviceIp ?? adapters
                //    .Where(x => x.Name == "eth0" || x.Name.Contains("以太网"))
                //    .Select(p => p.GetIPProperties())
                //    .SelectMany(p => p.UnicastAddresses)
                //    .Where(p => p.Address.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork && !System.Net.IPAddress.IsLoopback(p.Address))
                //    .Select(x => x.Address.ToString())
                //    .FirstOrDefault();
                //Console.WriteLine($"consul本地ip--{firstIpV4Address}");

                var agentServiceCheck = new AgentServiceCheck()
                {
                    DeregisterCriticalServiceAfter = TimeSpan.FromDays(1),
                    Interval = TimeSpan.FromSeconds(30),
                    HTTP = new Uri(new Uri($"http://{firstIpV4Address}:{consulOptions.servicePort}"), consulOptions.httpCheckPath).ToString(),
                    Timeout = TimeSpan.FromSeconds(10),
                    Status = HealthStatus.Warning,//设置初始状态
                };

                var registration = new AgentServiceRegistration()
                {
                    Checks = new[] { agentServiceCheck },
                    ID = $"{firstIpV4Address}:{consulOptions.servicePort}",
                    //ID = $"{firstIpV4Address}:{consulOptions.servicePort}_{Guid.NewGuid().ToString("n").Substring(0, 4)}",
                    Name = $"{(string.IsNullOrWhiteSpace(consulOptions.preServiceName) ? "" : $"[{consulOptions.preServiceName}]")}{consulOptions.serviceName}_{hostEvn.EnvironmentName}",
                    Address = firstIpV4Address,
                    Port = consulOptions.servicePort.Value,
                    Tags = new string[] { hostEvn.EnvironmentName, consulOptions.serviceName, firstIpV4Address, consulOptions.preServiceName },
                    Meta = new Dictionary<string, string>
                    {
                        {"MachineName",Environment.MachineName },
                        {"ProcessorCount",Environment.ProcessorCount.ToString() },
                        {"EnvironmentName",hostEvn.EnvironmentName },
                        {"minWorkerThreads",$"{minWorkerThreads}" },
                        {"minCompletionPortThreads",$"{minCompletionPortThreads}" },
                        {"maxWorkerThreads",$"{maxWorkerThreads}" },
                        {"maxCompletionPortThreads",$"{maxCompletionPortThreads}" },
                    },
                };

                lifetime.ApplicationStarted.Register(() =>
                {
                    consulClient.Agent.ServiceRegister(registration).ContinueWith(x =>
                    {
                        if (x.IsFaulted)
                        {
                            Console.WriteLine("注册consul异常," + x.Exception.ToString());
                        }
                        else
                        {
                            Console.WriteLine($"注册consul成功,{registration.ID},{registration.Name},{registration.Address},{registration.Port}");
                            lifetime.ApplicationStopping.Register(() =>
                            {
                                Console.WriteLine($"注销consul成功,{registration.ID},{registration.Name},{registration.Address},{registration.Port}");
                                consulClient.Agent.ServiceDeregister(registration.ID).GetAwaiter().GetResult();
                            });
                        }
                    });
                });

                //var delayTask = consulOptions.millisecondsDelay > 0 ? Task.Delay(consulOptions.millisecondsDelay) : Task.CompletedTask;
                //delayTask.ContinueWith(_ =>
                //{
                //    consulClient.Agent.ServiceRegister(registration).ContinueWith(x =>
                //    {
                //        if (x.IsFaulted)
                //        {
                //            Console.WriteLine("注册consul异常," + x.Exception.ToString());
                //        }
                //        else
                //        {
                //            Console.WriteLine($"注册consul成功,{registration.ID},{registration.Name},{registration.Address},{registration.Port}");
                //            lifetime.ApplicationStopping.Register(() =>
                //            {
                //                Console.WriteLine($"注销consul成功,{registration.ID},{registration.Name},{registration.Address},{registration.Port}");
                //                consulClient.Agent.ServiceDeregister(registration.ID).GetAwaiter().GetResult();
                //            });
                //        }
                //    });
                //});
            }
            catch (Exception ex)
            {
                Console.WriteLine($"consul注册异常,{ex}");
            }
        }
    }

    internal class WebClientx : WebClient
    {
        protected override WebRequest GetWebRequest(Uri address)
        {
            var result = base.GetWebRequest(address);
            result.Timeout = 5000;
            return result;
        }
    }
}
